<?php

namespace Hilaoyu\Utils;

/**
 * Created by laoyu.
 * User: hilaoyu@qq.com
 * Date: 2018/8/23
 * Time: 11:43
 */


use Hilaoyu\Utils\Traits\ErrorsTrait;
use Sunra\PhpSimple\HtmlDomParser;

class DocTrans
{
    use ErrorsTrait;
    static protected $command_binary = '/usr/local/bin/pandoc';
    static protected $allowed_from_types = [
        "docx" => [
            "name" => "(Word docx)",
            "extensions" => [
                'doc', 'docx'
            ]
        ],
        "epub" => [
            "name" => "(EPUB)",
            "extensions" => [
                'epub'
            ]
        ],
        "odt" => [
            "name" => "(ODT)",
            "extensions" => [
                'odt'
            ]
        ],
        /*"org" => [
            "name" => "(Emacs Org mode)",
            "extensions" => [

            ]
        ],*/
        "json" => [
            "name" => "(JSON version of native AST)",
            "extensions" => [
                'json'
            ]
        ],
        "docbook" => [
            "name" => "(DocBook)",
            "extensions" => [
                'xml'
            ]
        ],
        /*"latex" => [
            "name" => "(LaTeX)",
            "extensions" => [

            ]
        ],*/
        "html" => [
            "name" => "(HTML)",
            "extensions" => [
                'html'
            ]
        ],
        "markdown" => [
            "name" => "(Pandoc’s Markdown)",
            "extensions" => [
                'md'
            ]
        ],
        /*"commonmark" => [
            "name" => "(CommonMark Markdown)",
            "extensions" => [

            ]
        ],
        "creole" => [
            "name" => "(Creole 1.0)",
            "extensions" => [

            ]
        ],
        "dokuwiki" => [
            "name" => "(DokuWiki markup)",
            "extensions" => [

            ]
        ],
        "fb2" => [
            "name" => "(FictionBook2 e-book)",
            "extensions" => [

            ]
        ],
        "gfm" => [
            "name" => "(GitHub-Flavored Markdown)",
            "extensions" => [

            ]
        ],
        "haddock" => [
            "name" => "(Haddock markup)",
            "extensions" => [

            ]
        ],
        "ipynb" => [
            "name" => "(Jupyter notebook)",
            "extensions" => [

            ]
        ],
        "jats" => [
            "name" => "(JATS XML)",
            "extensions" => [

            ]
        ],
        "markdown_mmd" => [
            "name" => "(MultiMarkdown)",
            "extensions" => [

            ]
        ],
        "markdown_phpextra" => [
            "name" => "(PHP Markdown Extra)",
            "extensions" => [

            ]
        ],
        "markdown_strict" => [
            "name" => "(original unextended Markdown)",
            "extensions" => [

            ]
        ],
        "mediawiki" => [
            "name" => "(MediaWiki markup)",
            "extensions" => [

            ]
        ],
        "man" => [
            "name" => "(roff man)",
            "extensions" => [

            ]
        ],
        "muse" => [
            "name" => "(Muse)",
            "extensions" => [

            ]
        ],
        "native" => [
            "name" => "(native Haskell)",
            "extensions" => [

            ]
        ],
        "opml" => [
            "name" => "(OPML)",
            "extensions" => [

            ]
        ],
        "rst" => [
            "name" => "(reStructuredText)",
            "extensions" => [

            ]
        ],
        "t2t" => [
            "name" => "(txt2tags)",
            "extensions" => [

            ]
        ],
        "textile" => [
            "name" => "(Textile)",
            "extensions" => [

            ]
        ],
        "tikiwiki" => [
            "name" => "(TikiWiki markup)",
            "extensions" => [

            ]
        ],
        "twiki" => [
            "name" => "(TWiki markup)",
            "extensions" => [

            ]
        ],
        "vimwiki" => [
            "name" => "(Vimwiki)",
            "extensions" => [

            ]
        ]*/
    ];

    static protected $allowed_to_types = [
//        'asciidoc' => '(AsciiDoc)',
//        'beamer' => '(LaTeX beamer slide show)',
//        'commonmark' => '(CommonMark Markdown)',
//        'context' => '(ConTeXt)',
        'docbook' => '(DocBook 4)',
        'docbook5' => '(DocBook 5)',
        'docx' => '(Word docx)',
//        'dokuwiki' => '(DokuWiki markup)',
        'epub' => '(EPUB v3 book)',
//        'epub2' => '(EPUB v2)',
//        'fb2' => '(FictionBook2 e-book)',
//        'gfm' => '(GitHub-Flavored Markdown), or the deprecated and less accurate markdown_github; use markdown_github only if you need extensions not supported in gfm.',
//        'haddock' => '(Haddock markup)',
        'html' => '(HTML, i.e. HTML5/XHTML polyglot markup)',
//        'html4' => '(XHTML 1.0 Transitional)',
//        'icml' => '(InDesign ICML)',
//        'jats' => '(JATS XML)',
        'json' => '(JSON version of native AST)',
//        'latex' => '(LaTeX)',
//        'man' => '(groff man)',
        'markdown' => '(Pandoc’s Markdown)',
//        'markdown_mmd' => '(MultiMarkdown)',
//        'markdown_phpextra' => '(PHP Markdown Extra)',
//        'markdown_strict' => '(original unextended Markdown)',
//        'mediawiki' => '(MediaWiki markup)',
//        'ms' => '(groff ms)',
//        'muse' => '(Muse),',
//        'native' => '(native Haskell),',
        'odt' => '(OpenOffice text document)',
//        'opml' => '(OPML)',
//        'opendocument' => '(OpenDocument)',
//        'org' => '(Emacs Org mode)',
        'plain' => '(plain text),',
        'pptx' => '(PowerPoint slide show)',
//        'rst' => '(reStructuredText)',
        'rtf' => '(Rich Text Format)',
//        'texinfo' => '(GNU Texinfo)',
//        'textile' => '(Textile)',
//        'slideous' => '(Slideous HTML and JavaScript slide show)',
//        'slidy' => '(Slidy HTML and JavaScript slide show)',
//        'dzslides' => '(DZSlides HTML5 + JavaScript slide show),',
//        'revealjs' => '(reveal.js HTML5 + JavaScript slide show)',
//        's5' => '(S5 HTML and JavaScript slide show)',
//        'tei' => '(TEI Simple)',
//        'zimwiki' => '(ZimWiki markup)'
    ];


    protected $from = '';
    protected $from_type = '';
    protected $to = '';
    protected $to_type = '';

    protected $extract_media_path = '';
    protected $resource_paths = [];


    protected $standalone = true;
    protected $extend_options = [];

    /**
     * @param string $key
     * @return array|mixed|null
     */
    static public function getAllowedFromType($key = '')
    {
        $key = trim($key);

        if ($key) {
            return isset(static::$allowed_from_types[$key]) ? static::$allowed_from_types[$key] : null;
        }

        return static::$allowed_from_types;
    }

    static public function getAllowedFromTypeExtension($extension = '')
    {
        $extensions = [];


        foreach (static::getAllowedFromType() as $from_type => $setting) {
            if ($type_extensions = UtilArr::get($setting, 'extensions')) {
                $type_extensions = (array)$type_extensions;

                foreach ($type_extensions as $ext) {
                    $ext = trim($ext);
                    $extensions[$ext] = $from_type;
                }
            }
        }

        $extension = trim($extension);

        if ($extension) {
            return UtilArr::get($extensions, $extension);
        }

        return $extensions;
    }

    /**
     * @param string $key
     * @return array|mixed|null
     */
    static public function getAllowedToType($key = '')
    {
        $key = trim($key);

        if ($key) {
            return isset(static::$allowed_to_types[$key]) ? static::$allowed_to_types[$key] : null;
        }

        return static::$allowed_to_types;
    }

    /**
     * @return string
     */
    static public function binary()
    {
        return static::$command_binary;
    }

    static public function mediaTagToHtml($content, $media_base_path = '')
    {
        $content = trim($content);
        //清理换行等
        $rule_newline = array("\t", "\r\n", "\r", "\n");
        $content = str_replace($rule_newline, '', $content);
        if (empty($content)) {
            return $content;
        }

        $media_tags = ['video', 'audio'];
        $media_tags = implode('|', $media_tags);
        $pattern = "/(" . $media_tags . "):(.*?):(" . $media_tags . ")/isS";
        $content = preg_replace_callback($pattern, function ($matches) use ($media_base_path) {

            $html_tag = strtolower($matches[1]);

            $res_src = $matches[2];

            if (!filter_var($res_src, FILTER_VALIDATE_URL)) {
                $res_src = $media_base_path . '/' . $res_src;
                $res_src = UtilStr::clearRepeatedChar($res_src, ['\\', '/'], '/');
            }

            $dom = HtmlDomParser::str_get_html('<' . $html_tag . ' controls="controls" style="width:100%;">您的浏览器不支持 ' . $html_tag . ' 标签。</' . $html_tag . '>');
            $element = $dom->getElementByTagName($html_tag);
            $element->setAttribute('src', $res_src);

            $html = $dom->save();
            $dom->clear();
            unset($dom);
            return $html;
        }, $content);


        $dom = HtmlDomParser::str_get_html($content);
        if($dom){
            $elements = $dom->find('embed,img,video,audio');

            if (!empty($elements)) {
                foreach ($elements as $element) {


                    $data_path = $element->getAttribute('data-path');
                    $src = $element->getAttribute('src');

                    $file_path = $data_path ? $data_path : $src;

                    $file_extension = UtilFile::getExtension($file_path);

                    //公式图片
                    if('embed' == $element->tag && in_array($file_extension,['wmf'])){
                        $element->tag = 'img';
                    }
                    if(!filter_var($file_path,FILTER_VALIDATE_URL)){
                        $element->setAttribute('data-path', $file_path);
                    }

                }
            }

            $content = $dom->save();
            $dom->clear();
            unset($dom);
        }

        return $content;
    }


    static public function mediaTagToDoc($content, $relative_path_base = '')
    {
        $content = trim($content);
        //清理换行等
        $rule_newline = array("\t", "\r\n", "\r", "\n");
        $content = str_replace($rule_newline, '', $content);
        if (empty($content)) {
            return $content;
        }

        $dom = HtmlDomParser::str_get_html($content);

        $elements = $dom->find('video,audio');
        if (!empty($elements)) {
            foreach ($elements as $element) {
                $data_path = $element->getAttribute('data-path');
                $src = $element->getAttribute('src');

                $file_path = $data_path ? $data_path : $src;
                $Fs = FileService::make($file_path);

                $res_path = $Fs->path(true);
                if ($relative_path_base && (false !== strpos($res_path, $relative_path_base))) {
                    $res_path = substr($res_path, strlen($relative_path_base));
                }

                $element->outertext = $element->tag . ':' . $res_path . ':' . $element->tag;
            }
        }

        $content = $dom->save();
        $dom->clear();
        unset($dom);

        return $content;
    }

    /**
     * DocTrans constructor.
     * @param string $from file path
     * @param string $from_type
     */
    public function __construct($from = '', $from_type = '')
    {
        $this->from($from, $from_type);
    }

    public function __destruct()
    {
        $this->transCacheClear();
    }

    public function from($from, $type = '')
    {
        $this->transCacheClear();
        $from = trim($from);
        $type = trim($type);
        if ($from) {
            $this->from = $from;
            if (!$type && $ext = UtilFile::getExtension($from)) {
                $type = static::getAllowedFromTypeExtension($ext);
            }
        }


        if ($type && static::getAllowedFromType($type)) {
            $this->from_type = $type;
        }

        return $this;
    }

    public function toType($type = '')
    {
        $type = trim($type);

        if (!static::getAllowedToType($type)) {
            $this->setError('to type not allowed:' . $type);
            return false;
        }
        $this->to_type = $type;
        return $this;
    }

    /**
     * @param bool $standalone
     * @return $this
     */
    public function standalone($standalone = true)
    {
        if ((bool)$this->standalone != (bool)$standalone) {
            $this->transCacheClear();
        }
        $this->standalone = (bool)$standalone;
        return $this;
    }

    public function setExtractMediaPath($path)
    {
        $path = trim($path);
        $this->extract_media_path = $path;
        $this->addResourcePath($path);
        return $this;
    }

    public function getExtractMediaPath()
    {
        if ($this->extract_media_path) {
            return $this->extract_media_path;
        }

        return UtilFile::dirname($this->from);
    }

    public function addResourcePath($paths)
    {
        $paths = (array)$paths;
        foreach ($paths as $path) {
            $path = trim($path);
            if ($path) {
                $this->resource_paths[$path] = $path;
            }

        }

        return $this;
    }

    public function removeResourcePath($path = '')
    {
        $path = trim($path);
        if ($path) {
            unset($this->resource_paths[$path]);
        } else {
            $this->resource_paths = [];
        }
        return $this;
    }

    public function getResourcePath($forCommand = false)
    {
        $paths = (array)$this->resource_paths;
        if ($forCommand) {
            $glue = (strtolower(substr(PHP_OS, 0, 3)) === 'win') ? ';' : ':';
            if ($form_path = UtilFile::dirname($this->from)) {
                $paths[$form_path] = $form_path;
            }

            return join($glue, $paths);
        }

        return $paths;
    }

    public function setExtendOptions($option, $value = '')
    {
        if (is_array($option)) {
            $this->extend_options = array_merge($this->extend_options);
        } elseif (is_string($option) && !empty($option)) {
            $option = trim($option);
            $this->extend_options[$option] = @trim($value);
        }

        return $this;
    }

    public function buildCommand()
    {
        $command = [
            static::binary(),
            //'-s'
        ];

        if ($this->standalone) {
            $command[] = '-s';
        }

        if ($this->from_type) {
            $command[] = '-f ' . $this->from_type;
        }

        if ($this->to_type) {
            $command[] = '-t ' . $this->to_type;
        }
        /*if($path = $this->getDataDir()){
            $command[] = '--data-dir=' . $path;
        }*/
        if ($path = $this->getExtractMediaPath()) {
            $command[] = '--extract-media=' . $path;

        }
        $command[] = '--resource-path=' . $this->getResourcePath(true);

        if (!empty($this->extend_options)) {
            foreach ($this->extend_options as $option => $value) {
                $command[] = $option . ($value ? ('=' . $value) : '');
            }
        }

        $command[] = $this->from;

        if ($this->to) {
            $command[] = '-o ' . $this->to;
        }

        return implode(' ', $command);
    }


    protected function transCacheFileName($type)
    {
        $type = trim($type);
        if (!static::getAllowedToType($type)) {
            //$this->setError('to type not allowed:' . $type);
            return false;
        }

        if (!$this->from) {
            //$this->setError('源文件不存在2');
            return false;
        }

        return $this->getExtractMediaPath() . DIRECTORY_SEPARATOR . md5($this->from) . '.' . $type;
    }

    protected function transCacheGet($type)
    {
        $type = trim($type);
        if (!$type) {
            return null;
        }

        if (!$cache_file = $this->transCacheFileName($type)) {
            return null;
        }

        return file_exists($cache_file) ? $cache_file : null;
    }

    protected function transCacheClear()
    {
        foreach (array_keys(static::getAllowedToType()) as $type) {
            if ($cache_file = $this->transCacheFileName($type)) {
                UtilFile::delDirOrFile($cache_file);
            }
        }
    }


    public function to($type, $file = '')
    {

        if (!$this->from || !(UtilCli::nullDrive() || file_exists($this->from))) {
            $this->setError('源文件不存在1');
            return false;
        }

        $file = trim($file);
        $type = trim($type);

        if (!$this->toType($type)) {
            return false;
        }

        if (!$cache_file = $this->transCacheGet($type)) {
            if (!$cache_file = $this->transCacheFileName($type)) {
                return false;
            }

            if (!UtilFile::checkDir(UtilFile::dirname($cache_file))) {
                $this->setError('临时目录不可写');
                return false;
            }

            $this->to = $cache_file;

            $command = $this->buildCommand();


            //dump($command);

            $command_output = '';


            $status = UtilCli::execute($command, $command_output, null, '');

            if (!$status || !file_exists($cache_file)) {
                $this->setError($command_output);
                return false;

            }

            if (in_array(strtolower($type), ['html', 'html4'])) {
                $file_content = file_get_contents($cache_file);
                $file_content = static::mediaTagToHtml($file_content, $this->getExtractMediaPath());
                if (false === file_put_contents($cache_file, $file_content)) {
                    $this->setError('写入缓存文件失败');
                    return false;
                }
            }
        }

        if ($file) {
            return @copy($cache_file, $file);
        }

        return file_get_contents($cache_file);
    }

    public function baseJson($decode_to_array = false)
    {
        $docTrans = new static(UtilCli::nullDrive(), '');
        $docTrans->setExtractMediaPath(sys_get_temp_dir());
        $json = $docTrans->to('json');
        if (!$json) {
            $this->setError($docTrans->getError());
        }

        unset($docTrans);
        if ($decode_to_array) {
            $json = json_decode($json, true);
        }

        return $json ? $json : [];
    }


    protected function jsonTo(array $json, $type = 'html', $standalone = false)
    {
        $json = array_merge($this->baseJson(true), $json);
        $json['meta'] = array_merge([
            "subject" => [
                "t" => "MetaInlines",
                "c" => [
                    [
                        "t" => "Str",
                        "c" => ""
                    ]
                ]
            ],
            "rights" => [
                "t" => "MetaInlines",
                "c" => [
                    [
                        "t" => "Str",
                        "c" => ""
                    ]
                ]
            ],
            "identifier" => [
                "t" => "MetaInlines",
                "c" => [
                    [
                        "t" => "Str",
                        "c" => ""
                    ]
                ]
            ],
            "date" => [
                "t" => "MetaInlines",
                "c" => [
                    [
                        "t" => "Str",
                        "c" => ""
                    ]
                ]
            ],
            "language" => [
                "t" => "MetaInlines",
                "c" => [
                    [
                        "t" => "Str",
                        "c" => "zh-CN"
                    ]
                ]
            ],
            "author" => [
                "t" => "MetaInlines",
                "c" => [
                    [
                        "t" => "Str",
                        "c" => ""
                    ]
                ]
            ],
            "title" => [
                "t" => "MetaInlines",
                "c" => [
                    [
                        "t" => "Str",
                        "c" => ""
                    ]
                ]
            ],
            "publisher" => [
                "t" => "MetaInlines",
                "c" => [
                    [
                        "t" => "Str",
                        "c" => ""
                    ]
                ]
            ],
            "description" => [
                "t" => "MetaInlines",
                "c" => [
                    [
                        "t" => "Str",
                        "c" => ""
                    ]
                ]
            ],
            "description" => [
                "t" => "MetaInlines",
                "c" => [
                    [
                        "t" => "Str",
                        "c" => ""
                    ]
                ]
            ]
        ], UtilArr::get($json, 'meta', []));

        $temp_json_file = $this->getExtractMediaPath() . DIRECTORY_SEPARATOR . uniqid('to_html_') . '.json';
        file_put_contents($temp_json_file, json_encode($json));

        $docTrans = new static($temp_json_file, 'json');
        $docTrans->standalone($standalone);
        $html = $docTrans->to($type);
        if (!$html) {
            $this->setError('转档内容失败:' . $docTrans->getError(false));
        }

        //dd($style,$content);
        $docTrans = null;
        unset($docTrans);

        return $html;

    }


    public function getMetas()
    {
        $meta = [];
        $this->standalone(true);
        $doc_json = $this->to('json');
        $doc_json = json_decode($doc_json, true);
        if ($doc_json) {
            unset($doc_json['blocks']);

            $content = $this->jsonTo($doc_json, 'markdown', true);
            preg_replace_callback('/(.*?)\:(.*)/', function ($matches) use (&$meta) {
                $key = trim($matches[1]);
                $value = trim($matches[2]);
                if ($key) {
                    $meta[$key] = $value;
                }
            }, $content);

        }
        return $meta;

    }
    public function getHtmlStyle()
    {
        $style = '';
        $this->standalone(true);
        $doc_json = $this->to('json');
        $doc_json = json_decode($doc_json, true);
        if ($doc_json) {
            unset($doc_json['blocks']);

            $content = $this->jsonTo($doc_json, 'html', true);
            if (preg_match('/\<style\>(.+)\<\/style\>/', $content, $matches)) {
                $style = $matches[1];
            }

        }
        return trim($style);

    }

    public function toHtmlBlock(\Closure $progressCallback, $max_size = -1)
    {
        $max_size = intval($max_size);

        $this->standalone(true);
        $doc_json = $this->to('json');

        if (!($doc_json = json_decode($doc_json, true))) {
            $this->setError('文件解析失败');
            return false;
        }


        $flag = true;

        try {
            $blocks = UtilArr::get($doc_json, 'blocks', []);
            unset($doc_json['blocks']);
            $item_count = count($blocks);
            $last_header_depth = 0;

            $i = 0;
            while (!empty($blocks)) {
                $i++;
                if ($i > 10) {
                    //exit();
                }

                $block_finished = false;
                $part_is_catalog = false;
                $part_depth = 1;
                $content = '';
                do {
                    $block = array_shift($blocks);
                    if ($block['t'] == 'Header') {
                        if (!empty($content)) {
                            array_unshift($blocks, $block);
                            break;
                        }
                        $part_is_catalog = true;
                        $last_header_depth = $part_depth = UtilArr::first(UtilArr::get($block, 'c', []), null, 1);
                        $block_finished = true;
                    } else {
                        if (intval($last_header_depth) > 0) {
                            $part_depth = intval($last_header_depth) + 1;
                        } else {
                            $part_depth = 1;
                        }
                    }


                    $doc_json['blocks'] = [$block];

                    $content .= $this->jsonTo($doc_json, 'html', false);
                    if (
                        0 == $max_size
                        || ($max_size > 0 && strlen($content) > $max_size)
                    ) {
                        $block_finished = true;
                    }

                    if (empty($blocks)) {
                        $block_finished = true;
                    }

                } while (!$block_finished);


                //dump('=='.$i,$chapter);continue;
                $paragraph = [
                    'content' => $content,
                    'depth' => $part_depth,
                    'is_catalog' => $part_is_catalog,
                ];

                //进度回调
                $item_count_current = count($blocks);

                $percent = intval((($item_count - $item_count_current) / $item_count) * 100);

                if (false === call_user_func($progressCallback, $paragraph, $percent)) {
                    break;
                }
                //dump($percent_prev,$content);

            }


        } catch (\Exception $e) {

            $this->setError($e->getMessage());
            $flag = false;
        }

        return $flag;
    }





}